#include <fstream>
#include <algorithm>
#include <cmath>
#include "vizulizator.h"
#include "glext.h"

Vizulizator::Vizulizator(QWidget *parent)
	: QGLWidget(QGLFormat(QGL::SampleBuffers), parent)
	, shadowTexX(800)
	, shadowTexY(600)
{
	
	makeCurrent();

	if(0 == glActiveTextureARB)
		glActiveTextureARB  = (void (APIENTRY *) (GLenum)) context()->getProcAddress("glActiveTextureARB");

	startTimer(10);

	camera = new Camera(-0.63, 0.19, 35.0);
	light = new Light(GL_LIGHT0);
	ship = new Ship(context());
	ship->loadViewModel("./models/cgtg/boat.vis");
	water = new Water(context());
}

Vizulizator::~Vizulizator()
{
}

QSize Vizulizator::minimumSizeHint() const
{
    return QSize(400, 300);
}

QSize Vizulizator::sizeHint() const
{
    return QSize(800, 600);
}

int Vizulizator::heightForWidth(int w) const
{
	return w*3/4;
}

void Vizulizator::setShipRenderData(const ShipRenderData& shipRenderData)
{
	this->shipRenderData = shipRenderData;
}

void Vizulizator::initializeGL()
{
	glClearColor(0.9f, 1.0f, 1.0f, 0.0f);
	glEnable(GL_DEPTH_TEST);
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

	glDepthFunc(GL_LEQUAL);
	glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	ship->loadTextures(this);
	water->loadTextures(this);

	light->applyLight();


	CreateShadowTex(shadowTexX, shadowTexY, shadowMap);

	bool shaderLoaded = ship->loadShaders();
	shaderLoaded = shaderLoaded && water->loadShaders();
}

void Vizulizator::paintGL()
{
	std::vector<float> lightProj(16, 0);
	std::vector<float> lightView(16, 0);

	light->getLightMatrices(lightProj, lightView);

	glEnable(GL_CULL_FACE);

	GenerateShadowTex(shadowTexX, shadowTexY, &lightProj[0], &lightView[0], shadowMap);

	camera->look();

	light->applyPosition();

	ApplyShadowTex(GL_TEXTURE2_ARB, shadowMap, &lightProj[0], &lightView[0]);	

	ship->draw(shipRenderData, GL_TEXTURE2_ARB);

	glDisable(GL_CULL_FACE);


	glEnable(GL_BLEND);
	water->draw(shipRenderData, camera->getPosition());
	glDisable(GL_BLEND);
}

void Vizulizator::resizeGL(int width, int height)
{
	if (height == 0)
		height = 1;

	glViewport(0, 0, width, height);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(45.0f, (GLfloat)width / (GLfloat)height, 0.1f, 1000.0f);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}

void Vizulizator::mousePressEvent(QMouseEvent *e)
{
	lastPos = e->pos();
}

void Vizulizator::mouseMoveEvent(QMouseEvent *e)
{
	int dx = e->x() - lastPos.x();
    int dy = e->y() - lastPos.y();

	if (e->buttons() & Qt::LeftButton) {
		camera->rotateX(0.01*dy);
        camera->rotateY(0.01*dx);
    }
	lastPos = e->pos();
	updateGL();
}

void Vizulizator::wheelEvent(QWheelEvent *e)
{
	e->delta() > 0 ? camera->addDistance(2.0) : camera->addDistance(-2.0);
	updateGL();
}	

void Vizulizator::keyPressEvent(QKeyEvent *e)
{
	/*if (e->key() == Qt::Key_Up)
		test1 += 0.5f;
	else if (e->key() == Qt::Key_Right)
		test2 += 0.5f;
	else if (e->key() == Qt::Key_Down)
		test1 -= 0.5;
	else if (e->key() == Qt::Key_Left)
		test2 -= 0.5f;

	updateGL();*/
}

void Vizulizator::timerEvent(QTimerEvent *e)
{
	updateGL();
}

void Vizulizator::CreateShadowTex(const int width, const int height, GLuint& shadowMap)
{
	glGenTextures(1, &shadowMap);								
	glBindTexture(GL_TEXTURE_2D, shadowMap);

	glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, width, height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, 0);

	glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
	glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);

	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE_ARB);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC_ARB, GL_LEQUAL);
}

void Vizulizator::GenerateShadowTex(const int width, const int height, const float* projection, const float* modelView, GLuint& shadowMap)
{
	float viewPort[4] = {0};
	glGetFloatv(GL_VIEWPORT, viewPort);	

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glViewport(0, 0, width, height);

	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadMatrixf(projection);

	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glLoadMatrixf(modelView);

	glPolygonOffset(0.0, 0.0);
	
	glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
	glEnable(GL_POLYGON_OFFSET_FILL);
	glCullFace(GL_FRONT);

	ship->draw(shipRenderData);

	glBindTexture(GL_TEXTURE_2D, shadowMap);
	glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, width, height);

	glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
	glDisable(GL_POLYGON_OFFSET_FILL);
	glCullFace(GL_BACK);

	glPopMatrix();
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();

	glMatrixMode(GL_MODELVIEW);

	glViewport(viewPort[0], viewPort[1], viewPort[2], viewPort[3]);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}

void Vizulizator::ApplyShadowTex(const GLenum texNum, GLuint shadowMap, const float* projection, const float* modelView)
{
	glActiveTextureARB(GL_TEXTURE2_ARB);
    glBindTexture(GL_TEXTURE_2D, shadowMap);
	

	const float bias[] = {0.5, 0.0, 0.0, 0.0, 
						  0.0, 0.5, 0.0, 0.0,
						  0.0, 0.0, 0.5, 0.0,
						  0.5, 0.5, 0.5, 1.0};

	glMatrixMode(GL_TEXTURE);
	glLoadMatrixf(bias);
	glMultMatrixf(projection);
	glMultMatrixf(modelView);
	glMultMatrixf(&(camera->getInverse())[0]);


	glMatrixMode(GL_MODELVIEW);

}